package work.linruchang.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 增强的HTML处理工具
 *
 * @author LinRuChang
 * @version 1.0
 * @date 2022/08/16
 * @since 1.8
 **/
@Slf4j
public class EnhanceHtmlUtil {

    public static final String TAG_SCRIPT_NAME = "script";
    public static final String TAG_LINK_NAME = "link";
    public static final String TAG_IMG_NAME = "img";
    public static final String TAG_IMG_A = "a";


    public static final String ATTRIBUTE_SRC_NAME = "src";
    public static final String ATTRIBUTE_HREF_NAME = "href";


    /**
     * 获取网页的标题信息
     *
     * @param linkUrl
     * @return
     */
    public static String getTitle(String linkUrl) {
        Assert.isTrue(EnhanceHttpUtil.isHttpOrHttps(linkUrl));
        String htmlContent = null;
        try {
            htmlContent = HttpUtil.get(linkUrl);
            Document htmlDocument = Jsoup.parse(htmlContent);
            Elements title = htmlDocument.getElementsByTag("title");
            if (title != null && title.hasText()) {
                return StrUtil.trim(title.get(0).text());
            }
        } catch (Exception e) {
            log.warn("获取页面标题异常：{}", ExceptionUtil.stacktraceToOneLineString(e));
            if(StrUtil.isNotBlank(htmlContent)) {
                return StrUtil.trim(StrUtil.subBetween(htmlContent,"<title>","</title>"));
            }
        }
        return null;
    }


    /**
     * 获取<script>节点的src属性集
     *
     * @param baseUrl     基准URL == 用于填充如果链接是非http开头以及/开头的情况
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByScript(String baseUrl, String htmlContent) {
        return getArrtibutesByTag(htmlContent, TAG_SCRIPT_NAME, ATTRIBUTE_SRC_NAME).stream()
                .map(hyperlink -> StrUtil.blankToDefault(URLUtil.completeUrl(baseUrl, hyperlink), hyperlink))
                .collect(Collectors.toSet());
    }

    /**
     * 获取<script>节点的src属性集
     *
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByScript(String htmlContent) {
        return getHyperlinksByScript(null, htmlContent);
    }

    /**
     * 获取<link>节点的href属性集
     *
     * @param baseUrl     基准URL == 用于填充如果链接是非http开头以及/开头的情况
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByLink(String baseUrl, String htmlContent) {
        return getArrtibutesByTag(htmlContent, TAG_LINK_NAME, ATTRIBUTE_HREF_NAME).stream()
                .map(hyperlink -> StrUtil.blankToDefault(URLUtil.completeUrl(baseUrl, hyperlink), hyperlink))
                .collect(Collectors.toSet());
    }

    /**
     * 获取<link>节点的href属性集
     *
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByLink(String htmlContent) {
        return getHyperlinksByLink(null, htmlContent);
    }

    /**
     * 获取<img>节点的src属性集
     *
     * @param baseUrl     基准URL == 用于填充如果链接是非http开头以及/开头的情况
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByImg(String baseUrl, String htmlContent) {
        return getArrtibutesByTag(htmlContent, TAG_IMG_NAME, ATTRIBUTE_SRC_NAME).stream()
                .map(hyperlink -> StrUtil.blankToDefault(URLUtil.completeUrl(baseUrl, hyperlink), hyperlink))
                .collect(Collectors.toSet());
    }

    /**
     * 获取<img>节点的src属性集
     *
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByImg(String htmlContent) {
        return getHyperlinksByImg(null, htmlContent);
    }

    /**
     * 获取<a>节点的href属性
     *
     * @param baseUrl     基准URL == 用于填充如果链接是非http开头以及/开头的情况
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByA(String baseUrl, String htmlContent) {
        return getArrtibutesByTag(htmlContent, TAG_IMG_A, ATTRIBUTE_HREF_NAME).stream()
                .map(hyperlink -> StrUtil.blankToDefault(URLUtil.completeUrl(baseUrl, hyperlink), hyperlink))
                .collect(Collectors.toSet());
    }

    /**
     * 获取<a>节点的href属性
     *
     * @param htmlContent html内容
     * @return
     */
    public static Set<String> getHyperlinksByA(String htmlContent) {
        return getHyperlinksByA(null, htmlContent);
    }

    /**
     * 获取某个节点的某个属性值集合
     *
     * @param htmlContent   html内容
     * @param nodeName      节点名
     * @param attributeName 属性名
     * @return
     */
    private static Set<String> getArrtibutesByTag(String htmlContent, String nodeName, String attributeName) {
        if (StrUtil.isAllNotBlank(htmlContent, nodeName, attributeName)) {
            return Optional.ofNullable(htmlContent)
                    .map(html -> {
                        Document parse = Jsoup.parse(htmlContent);
                        return parse.getElementsByTag(nodeName).stream()
                                .map(scriptElem -> {
                                    return scriptElem.attr(attributeName);
                                })
                                .filter(StrUtil::isNotBlank)
                                .collect(Collectors.toSet());
                    }).orElse(CollectionUtil.newLinkedHashSet());
        }
        return CollectionUtil.newLinkedHashSet();
    }

    /**
     * 下载替换html文件里面的资源域名为static == 新html文件则在htmlFile下的【原文件主名_yyyyMMddHHmmss.原文件后缀】
     * 静态文件比如 img、script、link的文件到htmlPath所在目录的static文件夹
     *
     * @param htmlFile 目标html文件
     */
    public static void downloadStaticResourceAndReplace(File htmlFile) {
        downloadStaticResourceAndReplace(htmlFile, false);
    }


    /**
     * 下载替换html文件里面的资源域名为static
     * 静态文件比如 img、script、link的文件到htmlPath所在目录的static文件夹
     *
     * @param htmlFile              目标html文件
     * @param isCoverSourceFileFlag 是否覆盖原文件，false则创建新文件，新文件名为 原文件主名_yyyyMMddHHmmss.原文件后缀
     */
    public static void downloadStaticResourceAndReplace(File htmlFile, boolean isCoverSourceFileFlag) {
        Assert.isTrue(FileUtil.exist(htmlFile), "html文件缺失，请检查");
        String htmlContent = FileUtil.readString(htmlFile, StandardCharsets.UTF_8);
        String htmlFilePath = htmlFile.getAbsolutePath();
        String staticDirName = "static";

        Set<String> staticResDomainNames = Stream.of(getHyperlinksByImg(htmlContent), getHyperlinksByLink(htmlContent), getHyperlinksByScript(htmlContent))
                .flatMap(Collection::stream)
                .distinct()
                .map(StrUtil::trim)
                .filter(url -> StrUtil.startWith(url, "http"))
                .peek(resUrl -> {
                    String targetFile = null;
                    try {
                        String noProtocolUrl = StrUtil.splitTrim(resUrl, "//").get(1);
                        String uri = StrUtil.subAfter(noProtocolUrl, "/", false);
                        targetFile = StrUtil.format("{}/{}/{}", FileUtil.getParent(htmlFilePath, 1), staticDirName, uri);
                        boolean fileExistFlag = FileUtil.exist(targetFile);
                        if (!fileExistFlag) {
                            HttpUtil.downloadFile(resUrl, targetFile);
                        }
                        log.info("{}：{}==={}", fileExistFlag ? "本地存有" : "下载", resUrl, targetFile);
                    } catch (Exception e) {
                        log.error("异常下载：{}==={}", resUrl, targetFile);
                    }
                })
                .map(EnhanceUrlUtil::getDomainName)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toSet());

        for (String staticResDomainName : staticResDomainNames) {
            htmlContent = StrUtil.replace(htmlContent, staticResDomainName, staticDirName);
        }


        if (isCoverSourceFileFlag) { // 写入到原文件中
            FileUtil.writeString(htmlContent, htmlFile, StandardCharsets.UTF_8);
        } else { // 写入到新文件中
            String newFileName = StrUtil.format("{}_{}.{}", FileUtil.mainName(htmlFile), DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN), FileUtil.getSuffix(htmlFile));
            String newHtmlFilePath = StrUtil.format("{}/{}", FileUtil.getParent(htmlFile, 1), newFileName);
            FileUtil.writeString(htmlContent, newHtmlFilePath, StandardCharsets.UTF_8);
        }

    }

}
